%% modules.erl
%%      Compile abstract syntax trees to modules.
%%      Gather information from loaded modules.
%%
%% Copyright (C) 2008, 2009 Gábor Fehér
%%
%% This file is part of ECT.
%% 
%% ECT is free software: you can redistribute it and/or modify
%% it under the terms of the GNU General Public License as published by
%% the Free Software Foundation, either version 3 of the License, or
%% (at your option) any later version.
%% 
%% ECT is distributed in the hope that it will be useful,
%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
%% GNU General Public License for more details.
%% 
%% You should have received a copy of the GNU General Public License
%% along with ECT.  If not, see <http://www.gnu.org/licenses/>.
%%
-module(modules).
-export([is_available/2, get_attributes/2, compile_forms_to_file/4, get_local_classdata/3]).

%% Function: which(Module)
%% Purpose: locate a module on the disk (in the code path)
%% Arguments:
%%   Module::atom() - name of module
%% Returns:
%%   * a string() that is the full absolute path of the beam file of the module including name and extension,
%%   if if exists in the Erlang code path.
%%   * the atom non_exsitent otherwise
%% Note: the difference from code:which is that Module is searched in memory, only in the code path
which(Module) ->
    File = atom_to_list(Module)++".beam",
    code:where_is_file(File).

%% Function: is_available(Module, Src)
%% Purpose: detects if a module is available on disk/loaded into the memory
%% Arguments:
%%   Module::atom() - name of module
%%   Src - either disk or memory: the place where the module should be looked up
%% Returns:
%%   * true if Module exists in the code path and Src =:= disk
%%          or Module is loaded into the memory and Src =:= memory
%%   * false otherwise
is_available(Module, memory) ->
    case code:is_loaded(Module) of
    {file, _} -> true;
    false -> false
    end;
is_available(Module, disk) ->
    which(Module) =/= non_existing.

%% Function: get_attribures(Module, Src)
%% Purpose: gets the list of attributes from a module, either from disk or memory
%% Arguments:
%%   Module::atom() - name of module
%%   Src - either disk or memory: the place where the module should be looked up
%% Returns:
%%   List of attributes of Module, either from the disk from the code path, or from the memory
%%   depending on the value of Src. Format:
%%   [{AttrName, [Value]}] - each attribute have a list of values, which may also have one elements
get_attributes(Module, memory) ->
    case is_available(Module, memory) of
    true ->
        Module:module_info(attributes);
    false ->
        throw({ect_error, io_lib:format("module ~p is not loaded", [Module])})
    end;
get_attributes(Module, disk) ->
    Path = which(Module),
    case Path of
    non_existing ->
        throw({ect_error, io_lib:format("module ~p is not found on disk", [Module])});
    _ ->
        {ok, {Module, [{attributes, Attributes}]}} = beam_lib:chunks(Path, [attributes]),
        Attributes
    end.

%% Function: get_local_classdata(Module, Type, Source)
%% Purpose: gets the class related data (#class_local{} and list of loaded subclasses) for a class
%% Arguments:
%%   Module::atom() - name of module of a class
%%   Type - either proxy or impl - determines if Module should be treated as proxy or implementation module
%%   Src - either disk or memory: the place where the module should be looked up
%% Returns:
%%   {LocalClassData = #class_local{}, [LoadedSubClass]}
%%   The second item, the list of loaded subclasses is replaced by the atom 'unknown', if
%%   the class is accessed from the disk.
get_local_classdata(Module, impl, Source) ->
    ImplAttrs = modules:get_attributes(Module, Source),
    {local_class_data, [LocalClassData]} = proplists:lookup(local_class_data, ImplAttrs),
    {LocalClassData, unknown};
get_local_classdata(Module, proxy, Source) ->
    ProxyAttrs = modules:get_attributes(Module, Source),
    {impl_module,[ImplModule]} = proplists:lookup(impl_module, ProxyAttrs),
    {LocalClassData1, unknown} = get_local_classdata(ImplModule, impl, Source),
    {loaded_subclasses,LSC} = proplists:lookup(loaded_subclasses, ProxyAttrs),
    {LocalClassData1, LSC}.

%% Function: compile_forms_to_binary(Forms, Options, Id)
%% Purpose: given the abstract syntax tree of a module, this function compiles it stream of bytes representing beam code
%% Arguments:
%%   Forms - the abstract syntax tree of the module to compile
%%   Options - list of options passed to the Erlang compiler
%%   Id::string() - an identifier of the module, to be used in error messages
%% Returns:
%%   the BinaryOrCode output of the Erlang compiler
%% Throws:
%%   If an error occures while compiling, an {implCompileError, Forms} exception is thrown.
%%   (And an error message is printed.)
compile_forms_to_binary(Forms, Options2, Id) ->
    my_pp:printforms(".ectgen.erl", Forms, Options2),
    BinRet = compile:forms(Forms, Options2),
    case BinRet of
    {ok,_ModuleName,BinaryOrCode} -> BinaryOrCode;
    {ok,_ModuleName,BinaryOrCode,_Warnings} -> BinaryOrCode;
    _ ->
        io:format("[ect] Error while compiling "++Id++":~p~n", [BinRet]),
        throw({implCompileError, Forms})
    end.

%% Function: compile_forms_to_binary(Forms, Options, Module, Id)
%% Purpose: given the abstract syntax tree of a module, this function compiles it stream of bytes representing beam code
%% Arguments:
%%   Forms - the abstract syntax tree of the module to compile
%%   Options - list of options passed to the Erlang compiler
%%   Module::atom() - name of module
%%   Id::string() - an identifier of the module, to be used in error messages
%% Returns:
%%   the atom ok
%% Side effects:
%%   A file, in the output directory specified in Options, with base name Module and with extension .beam
%%   is created, and it contains the compiled binary of Forms. Other side effects of the compiler may
%%   also happen.
%% Throws:
%%   If an error occures while compiling, an {implCompileError, Forms} exception is thrown.
%%   (And an error message is printed.)

compile_forms_to_file(Forms, Options1, Module, Id) ->
    Options2 = Options1,
% uneccessary:
%         lists:filter(fun ({parse_transfrom,_}) -> false;
%                          (_) -> true
%                      end, Options1),

    OutDir2 = proplists:get_value(outdir, Options2, "."),
 

    M = atom_to_list(Module),

    ModuleBeam = OutDir2++M++".beam",
    Binary = compile_forms_to_binary(Forms, Options2, Id),
    ok = file:write_file(ModuleBeam, Binary).
